package com.github.cyf.ppt;

import com.github.cyf.ppt.model.Text;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.poi.sl.usermodel.PictureData;
import org.apache.poi.xslf.usermodel.*;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import java.awt.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
 * @author chenyifan
 * @create 2024-04-18 15:55
 */
@Slf4j
public class PPTUtil {

    // 替换标签需要设置与前后文本不一样的样式，使其在一个 TextRun 中
    public static final String GRAMER_PREFIX = "#{";

    public static final String GRAMER_SUFFIX = "}";

    public static final String GRAMER_SPLIT = "@@";

    /**
     * 读取 PPT
     * @param filePath 文件路径
     * @return
     */
    public static XMLSlideShow readPowerPoint(String filePath) {
        try(FileInputStream fis = new FileInputStream(filePath)) {
            return new XMLSlideShow(fis);
        } catch (Exception e) {
            throw new RuntimeException("读取PPT失败", e);
        }
    }

    /**
     * 导出 PPT 到指定目录
     * @param ppt
     * @param filePath 文件路径
     */
    public static void exportPowerPoint(XMLSlideShow ppt, String filePath) {
        File file = new File(filePath);
        if (!file.getParentFile().exists()) {
            file.getParentFile().mkdirs();
        }
        try(FileOutputStream fos = new FileOutputStream(filePath)) {
            ppt.write(fos);
        } catch (IOException e) {
            throw new RuntimeException("导出PPT失败", e);
        }
    }

    /**
     * 批量替换 PPT 的图片，从上到下，从左到右的顺序
     * @param ppt  PPT，页码从 1 开始
     * @param page 开始页数
     * @param imageList 图片路径列表
     */
    public static void replacePictureShape(XMLSlideShow ppt, int page, List<String> imageList) {
        XSLFSlide slide = ppt.getSlides().get(page - 1);
        List<XSLFShape> shapes = slide.getShapes();
        List<XSLFPictureShape> pictureShapes = new ArrayList<>();
        for (XSLFShape shape : shapes) {
            if (shape instanceof XSLFPictureShape) {
                pictureShapes.add((XSLFPictureShape) shape);
            }
        }
        pictureShapes.sort((a, b) -> {
            double aX = a.getAnchor().getX();
            double aY = a.getAnchor().getY();
            double bX = b.getAnchor().getX();
            double bY = b.getAnchor().getY();
            double sub = Math.abs(aY - bY);
            // y坐标误差在 50 算一个位置
            return sub < 50 ? Double.compare(aX, bX) : Double.compare(aY, bY);
        });
        for (int i = 0; i < Math.min(pictureShapes.size(), imageList.size()); i++) {
            setImgToPictureShape(pictureShapes.get(i), imageList.get(i));
        }
    }

    /**
     * 根据图片路径，将图片添加到图片框内
     * @param pictureShape 图片框
     * @param imgPath 图片路径
     * @return
     */
    private static void setImgToPictureShape(XSLFPictureShape pictureShape, String imgPath) {
        File img = Paths.get(imgPath).toFile();
        if (!img.exists()) {
            throw new RuntimeException("图片不存在，路径：" + imgPath);
        }
        try (FileInputStream fis = new FileInputStream(img)) {
            byte[] bytes = StreamUtils.copyToByteArray(fis);
            pictureShape.getPictureData().setData(bytes);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }


    /**
     * 根据配置添加图片到幻灯片
     * @param ppt
     * @param page 页码从 1 开始
     * @param imageList 图片路径列表
     * @param config 图片布局配置
     */
    public static void addImagesToSlide(XMLSlideShow ppt, int page, List<String> imageList, ImageGridLayout config) {
        // 读取图片文件并添加到PPT中
        int xCount = config.getXCount();
        int yCount = config.getYCount();
        int picWidth = config.getPicWidth();
        int picHeight = config.getPicHeight();
        int xStart = config.getXStart();
        int yStart = config.getYStart();
        int xGap = config.getXGap();
        int yGap = config.getYGap();
        int index = 0;
        for (int i = 0; i < yCount; i++) {
            for (int j = 0; j < xCount; j++) {
                if (index >= imageList.size()) {
                    break;
                }
                // x,y 为图片左上角坐标
                int x = xStart + j * (picWidth + xGap);
                int y = yStart + i * (picHeight + yGap);
                createPictureShape(ppt, page, x, y, picWidth, picHeight, imageList.get(index++));
            }
        }
    }

    /**
     * 添加图片框到幻灯片
     * @param ppt PPT
     * @param page 页码从 1 开始
     * @param x 图片框左上角 x 坐标
     * @param y 图片框左上角 y 坐标
     * @param picWidth 图片框宽度
     * @param picHeight 图片框高度
     * @param filePath 图片路径
     */
    public static void createPictureShape(XMLSlideShow ppt, int page, int x, int y, int picWidth, int picHeight, String filePath) {
        XSLFSlide slide = ppt.getSlides().get(page - 1);
        XSLFPictureData pictureData = null;
        try {
            String extension = "." + FilenameUtils.getExtension(filePath);
            for (PictureData.PictureType pictureType : PictureData.PictureType.values()) {
                if (pictureType.extension.equalsIgnoreCase(extension)) {
                    pictureData = ppt.addPicture(Paths.get(filePath).toFile(), pictureType);
                    break;
                }
            }
            if (pictureData == null) {
                throw new RuntimeException("不支持的图片格式：" + extension);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        XSLFPictureShape pictureShape = slide.createPicture(pictureData);
        pictureShape.setAnchor(new Rectangle(x, y, picWidth, picHeight));
    }


    /**
     * 批量设置文本片段样式
     * @param ppt PPT
     * @param page 页码从 1 开始
     * @param textMap key: 文本片段内容，value: 文本片段样式
     */
    public static void replaceText(XMLSlideShow ppt, int page, Map<String, Text> textMap) {
        XSLFSlide slide = ppt.getSlides().get(page - 1);
        List<XSLFShape> shapes = slide.getShapes();
        for (XSLFShape shape : shapes) {
            if (shape instanceof XSLFTextShape) {
                XSLFTextShape xslfTextShape = (XSLFTextShape) shape;
                List<XSLFTextParagraph> paragraphs = xslfTextShape.getTextParagraphs();
                paragraphs.forEach(paragraph -> replaceParagraph(paragraph, textMap));
            } else if (shape instanceof XSLFTable) {
                XSLFTable table = (XSLFTable) shape;
                for (XSLFTableRow tableRow : table) {
                    for (XSLFTableCell cell : tableRow.getCells()) {
                        List<XSLFTextParagraph> paragraphs = cell.getTextParagraphs();
                        paragraphs.forEach(paragraph -> replaceParagraph(paragraph, textMap));
                    }
                }
            }
        }
    }

    private static void replaceParagraph(XSLFTextParagraph paragraph, Map<String, Text> textMap) {
        List<XSLFTextRun> textRuns = paragraph.getTextRuns();
        XSLFTextRun firstTextRun = textRuns.get(0);
        for (XSLFTextRun textRun : textRuns) {
            Text text = textMap.get(clearGrammer(textRun.getRawText()));
            if (text != null) {
                // 如果样式为空，使用第一个文本片段的样式
                if (text.getFontFamily() == null) {
                    textRun.setFontFamily(firstTextRun.getFontFamily());
                }
                if (text.getFontSize() == null) {
                    textRun.setFontSize(firstTextRun.getFontSize());
                }
                log.debug("expression: {}, text: {}", textRun.getRawText(), text.getText());
                text.setStyleToTextRun(textRun);
            } else if (GRAMER_SPLIT.equals(textRun.getRawText())) {
                textRun.setText(null);
            }
        }
    }

    private static String clearGrammer(String text){
        if (!StringUtils.hasText(text) || !text.startsWith(GRAMER_PREFIX) || !text.endsWith(GRAMER_SUFFIX)) {
            return text;
        }
        return text.substring(GRAMER_PREFIX.length(), text.length() - GRAMER_SUFFIX.length());
    }
}